%%%-------------------------------------------------------------------
%%% @author mirahs(2892727493@qq.com)
%%% @doc
%%%     数学相关工具
%%% @end
%%%-------------------------------------------------------------------
-module(util_math).

-export([
    floor/1
    ,ceil/1
    ,rand_seed/0
    ,rand/2
    ,rand_seed/2
    ,rand_list/1
    ,rand_list_m/2
    ,rand_list_seed/1
    ,rand_list_one/2
    ,for/3
    ,check_range/3
    ,keyrand/2

    ,rand_splitInt/4
    ,rand_int/2
    ,red/4

    ,calc_all_page/2
    ,calc_page_list/3
]).

%% @spec floor(X) -> integer()
%% X = number()
%% @doc 取小于X的最大整数 
floor(X) ->
    T = erlang:trunc(X),
    case X < T of
        true -> T - 1;
        _ -> T
    end.

%% @spec ceil(X) -> integer()
%% X = number()
%% @doc 取大于X的最小整数
ceil(X) ->
    T = erlang:trunc(X),
    case X > T of
        true -> T + 1;
        _ -> T
    end.

%% @spec rand(Min, Max) -> integer()
%% Min = integer()
%% Max = integer()
%% @doc 产生一个介于Min到Max之间的随机整数
rand_seed(Min, Min) -> Min;
rand_seed(Max, Min) when Max > Min ->
    rand_seed(Min, Max) ;
rand_seed(Min, Max) ->
    case get("rand_seed") of
        undefined ->
            Seed = case catch rand_mgr:get_seed() of
                       N = {_, _, _} -> N;
                       _ -> util_time:get_now()
                   end,
            random:seed(Seed),
            put("rand_seed", Seed);
        _ ->
            ignore
    end,
    M = Min - 1,
    random:uniform(Max - M) + M.

%% @spec rand(Min, Max) -> integer()
%% Min = integer()
%% Max = integer()
%% @doc 产生一个介于Min到Max之间的随机整数，已获取随机种子的进程调用
rand(Min, Min) -> Min;
rand(Min, Max) ->
    M = Min - 1,
    random:uniform(Max - M) + M.

%% 获取随机种子
rand_seed() ->
    case get("rand_seed") of
        undefined ->
            Seed =
                case catch rand_mgr:get_seed() of
                    N = {_, _, _} -> N;
                    _ -> util_time:get_now()
                end,
            random:seed(Seed),
            put("rand_seed", Seed);
        _ -> ignore
    end.

%% @spec rand_list(L::list()) -> null | term()
%% @doc 从一个list中随机取出一项，已获取随机种子的进程调用
rand_list([]) -> null;
rand_list([I]) -> I;
rand_list(List) ->
    Idx = rand(1, length(List)),
    get_term_from_list(List, Idx).

%% @spec rand_list(L::list(), N :: integer()) -> null | [term()]
%% @doc 从一个list中随机取出多项，已获取随机种子的进程调用
rand_list_m(N, L) ->
    rand_list_m(N, L, []).

rand_list_m(_, [], Back) -> Back;
rand_list_m(N, _, Back) when N =< 0 -> Back;
rand_list_m(N, List, Back) ->
    Idx = rand(1, length(List)),
    E = get_term_from_list(List, Idx),
    rand_list_m(N - 1, lists:delete(E, List), [E | Back]).

%% @spec rand_list(L::list()) -> null | term()
%% @doc 从一个list中随机取出一项
rand_list_seed([]) -> null;
rand_list_seed([I]) -> I;
rand_list_seed(List) ->
    Idx = rand_seed(1, length(List)),
    get_term_from_list(List, Idx).

%% @spec rand_list(L::list(), Index::integer()) -> false | term()
%% @doc 从一个list中随机取出一项
%% Index为相对权重索引,
rand_list_one(List, Index) ->
    RandMax = lists:sum([element(Index, Val) || Val <- List]),
    RandomDomain = util_math:rand(1, RandMax),
    rand_list_one(RandomDomain, List, Index).
rand_list_one(RandomDomain, [H | T], Index)->
    Domain = element(Index, H),
    case RandomDomain =< Domain of
        true -> H;
        false -> rand_list_one(RandomDomain - Domain, T, Index)
    end;
rand_list_one(_RandomDomain, [], _) ->
    false.

get_term_from_list([H | _T], 1) -> H;
get_term_from_list([_H | T], Idx) ->
    get_term_from_list(T, Idx - 1).

%% @spec for(Begin::integer(), End::integer(), Fun::function()) -> ok 
%% @doc 模拟for循环
for(End, End, Fun) ->
    Fun(End),
    ok;
for(Begin, End, Fun) when Begin < End ->
    Fun(Begin),
    for(Begin + 1, End, Fun).

%% @spec check_range(Val, Min, Max) -> number()
%% Val = number()
%% Min = number()
%% Max = number()
%% @doc 取值范围限制
check_range(Val, Min, Max) ->
    if
        Val > Max -> Max;
        Val < Min -> Min;
        true -> Val
    end.

%% @doc 指定tuple中的权重位置随机选择一个
keyrand([], _Pos) -> null;
keyrand([I], _Pos) -> I;
keyrand(L, Pos) ->
    Sum = lists:foldl(fun(Tuple, Acc) -> erlang:element(Pos, Tuple) + Acc end, 0, L),
    Luck = util_math:rand(1, Sum),
    keyrand(L, Pos, Luck).

keyrand([H | L], Pos, Luck) ->
    Lottery = erlang:element(Pos, H),
    case Luck =< Lottery of
        true -> H;
        false -> keyrand(L, Pos, Luck - Lottery)
    end.

%% @doc 整数随机分裂成N个数字
%% @spec rand_int(Int, Count) -> List
%% Int = integer() :: 待分割数值
%% Count = integer() :: 分割的个数
rand_int(Int, Count) when Int < Count ->
    Base = lists:duplicate(Int, 1),
    Add  = lists:duplicate(Count - Int, 0),
    util_list:disorder(Base ++ Add);
rand_int(Int, Count) ->
    XX   = rand_seed(2, Count),
    Base = ceil(Int / XX / Count),
    Lack = Int - Base * Count,
    Packs= rand_int2(Lack, Count-1, []),
    [Base + R || R <- Packs].

rand_int2(Lack, 0, Packs) ->
    [Lack|Packs];
rand_int2(0, Num, Packs) ->
    rand_int2(0, Num-1, [0 | Packs]);
rand_int2(Lack, Num, Packs) ->
    Rand = Lack div Num,
    Temp = rand_seed(1,Rand),
    rand_int2(Lack-Temp, Num-1, [Temp | Packs]).



%% @doc 整数随机分裂成N个大于Min的数字
%% @spec rand_splitInt(Int, N, Min, Type) -> List
%% Int = integer() :: 待分割数值
%% N   = integer() :: 分割的个数
%% Min = integer() :: 最小值限定
%% Max = integer() :: 最大值限定
rand_splitInt(Int, N, Min, 0) ->
    util_list:disorder(splitInt_div(Int, N, Min));
rand_splitInt(Int, N, Min, Max) ->
    util_list:disorder(red(Int, N, Min, Max)).

%% 红包均分下限算法
splitInt_div(0, N, _Min) ->
    lists:duplicate(N, 0);
splitInt_div(Int, N, Min) when Int div N < Min ->
    DIV = Int div N,
    splitInt_div(Int, N, DIV);
splitInt_div(Int, N, Min) ->
    DIV = Int div N,
    REM = Int rem N,
    splitInt_div(Min, DIV, REM, N, []).
splitInt_div(_Min,Base,REM,1,List) ->
    [Base+REM|List];
splitInt_div(Min,Base,REM,N,List) ->
    Max = REM+Base,
    Rand = rand_seed(Min, Max),
    splitInt_div(Min,Base,Max-Rand,N-1,[Rand|List]).


%% 红包上下限算法
red(0, N, _Min, _Max) ->
    lists:duplicate(N, 0);
red(Int, N, Min, Max) when Int div N < Min ->
    DIV = Int div N,
    red(Int, N, DIV, Max);
red(Int, N, Min, Max) when Int div N > Max ->
    DIV = Int div N,
    red(Int, N, Min, DIV + 1);
red(Int, N, Min, Max) ->
    red(Int, N, Min, Max, []).

red(Int, 1, _Min, _Max, List) when Int > _Max ->
    Min = lists:min(List),
    Money = rand_seed(Min,_Max),
    red(Int + Min - Money, 1, _Min, _Max, [Money|List] -- [Min]);
red(Int, 1, _Min, _Max, List) when Int < _Min ->
    Max = lists:max(List),
    Money = rand_seed(_Min,Max),
    red(Int + Max - Money, 1, _Min, _Max, [Money|List] -- [Max]);
red(Int, 1, _Min, _Max, List) -> [Int|List];
red(Int, N, _Min, _Max, List) ->
    Max2 = max(_Min, min(_Max, round(Int / N * 2) )),
    Money= rand_seed(_Min,Max2),
    red(Int - Money, N - 1, _Min, _Max, [Money|List]).


%% 数据分页--总页数
calc_all_page(List, Len) ->
    Count = length(List),
    util_math:ceil(Count / Len).
%% 数据分页--取第几页
calc_page_list(Page,List, Len) ->
    lists:sublist(List, max(1, (Page-1)*Len + 1), Len).
